/*
 * format - haXe File Formats
 *
 * Copyright (c) 2008, The haXe Project Contributors
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package format.flv;
import neko.io.File;
import sys.io.FileSeek;
import format.flv.Data;
import haxe.io.BytesOutput;
import haxe.io.Bytes;
class Writer {

	var ch : neko.io.FileOutput;
	var videoCodec:Int;
	var audioCodec:Int;
	var frameType:Int;
	var meta:OnMetaData;

	public function new(o) {
		this.ch = o;
		o.bigEndian = true;
	}

	public function close() {
		
		ch.seek(13, FileSeek.SeekBegin);
		injectMeta(meta);
		ch.close();
	}

	public static function readHeader( ch : haxe.io.Input ) {
		ch.bigEndian = true;
		if( ch.readString(3) != 'FLV' )
			throw "Invalid signature";
		if( ch.readByte() != 0x01 )
			throw "Invalid version";
		var flags = ch.readByte();
		if( flags & 0xF2 != 0 )
			throw "Invalid type flags "+flags;
		var offset = ch.readUInt30();
		if( offset != 0x09 )
			throw "Invalid offset "+offset;
		var prev = ch.readUInt30();
		if( prev != 0 )
			throw "Invalid prev "+prev;
		return {
			hasAudio : (flags & 1) != 1,
			hasVideo : (flags & 4) != 1,
			hasMeta : (flags & 8) != 1,
		};
	}

	public function writeHeader( h : Header ) 
	{
		ch.writeString("FLV");
		ch.writeByte(0x01);
		ch.writeByte( (h.hasAudio?1:0) | (h.hasVideo?4:0) | (h.hasMeta?8:0) );
		ch.writeUInt30(0x09);
		ch.writeUInt30(0x00);
		injectMeta(null);
	}

	public function writeChunk( chunk : Data ) {
		var k, data, time;
		switch( chunk ) {
		case FLVAudio(d,t): k = 0x08; data = d; time = t;
			if(audioCodec==null)
			{
				var info = new haxe.io.BytesInput(data).readByte();//data.get(0) >> 4
				audioCodec=info >>4;
				meta.audiocodecid=audioCodec;
				//trace("AUDIO: codec:" + audioCodec);
			}
		case FLVVideo(d,t): k = 0x09; data = d; time = t;
			if(videoCodec==null)
			{
				var info = new haxe.io.BytesInput(data).readByte();//data.get(0) >> 4
				frameType=info>>4;
				//meta.frameType=frameType;
				videoCodec=info & 0xff>>4;
				meta.videocodecid=videoCodec;
				//trace("VIDEO frame type:" +frameType);
				//trace("VIDEO codec:" +videoCodec);
			}
		case FLVMeta(d,t): k = 0x12; data = d; time = t;
			//trace("META:" + new haxe.io.BytesInput(data).readByte());
		}
		ch.writeByte(k);
		ch.writeUInt24(data.length);
		ch.writeUInt24(time);
		ch.writeUInt30(0);
		
		ch.write(data);
		ch.writeUInt30(data.length + 11);
	}
	function injectMeta(?m:OnMetaData=null)
	{	
		var meta= m!=null?m:
		{
			duration:13.13,//Float,				//Length of the FLV in seconds.
			width:320.0,//Float,
			height:120.0,//Float,
			framerate:1.0,//Float,
			videodatarate:1.0,//Float,
			audiocodecid:audioCodec,//Float,
			videocodecid:videoCodec,//Float,	
			canSeekToEnd:true,//Bool,			//Indicates whether the last video frame is a key frame or not. 
			audiodelay:0.0,//Float,
			cuePoints:new Array(),//Array<OnCuePoint>,
		
			//optional
			lasttimestamp:1.0,//Float,
			lastkeyframetimestamp:1.0,//Float,
			metadatacreator:"hxis2"//String,
			//metadatadate:Date.now()//Date
		};
		this.meta=meta;
		var chunk = writeMetaChunk(MMeta(meta),0,0,0);
		switch(chunk)
		{ 
			case FLVMeta(d,t): 
				ch.writeByte(0x12);
				ch.writeUInt24(500);
				//ch.writeUInt24(d.length);
				ch.writeUInt24(t);
				ch.writeUInt30(0);
				ch.write(d);
				for(i in 0...500 - d.length)
					ch.writeByte(0);
				ch.writeUInt30(500+11);
				//ch.writeUInt30(d.length+11);
			default:
		}
	}
	public function writeMetaChunk ( meta : MetaData, time, timeExtended:Int=0, streamid:Int=0 )
	{
		var out:BytesOutput = new BytesOutput();
		var data:BytesOutput = new BytesOutput();
		//data.bigEndian = out.bigEndian = true;
		var amf_writer:format.amf.Writer = new format.amf.Writer(data);
		var k = 0x12;
		switch (meta)
		{
			case MMeta(v):
				amf_writer.write( format.amf.Tools.encode("onMetaData") );
				var values = format.amf.Tools.encode(v);
				//trace(values);
				amf_writer.write(values);
				k = 0x12;
			case MCuePoint(c):
				amf_writer.write( format.amf.Tools.encode("onCuePoint") );
				var values = format.amf.Tools.encode(c);
				amf_writer.write(values);
				k = 0x12;
			case MXMP(x):
				amf_writer.write( format.amf.Tools.encode("onXMPData") );
				var values = format.amf.Tools.encode(x);
				amf_writer.write(values);
				k = 0x12;
			case MCustom(n, d, _k):
				amf_writer.write( format.amf.Tools.encode(n) );
				var values = format.amf.Tools.encode(d);
				amf_writer.write(values);
				k = _k;
		}
		var d : Bytes = data.getBytes();
		
		return FLVMeta(d, time);//, timeExtended, streamid)); 
	}
	public static function writeMetaTag ( meta : MetaData, time, timeExtended:Int=0, streamid:Int=0 )
	{
		var out:BytesOutput = new BytesOutput();
		var data:BytesOutput = new BytesOutput();
		//data.bigEndian = out.bigEndian = true;
		var amf_writer:format.amf.Writer = new format.amf.Writer(data);
		var k = 0x12;
		switch (meta)
		{
			case MMeta(v):
				amf_writer.write( format.amf.Tools.encode("onMetaData") );
				var values = format.amf.Tools.encode(v);
				//trace(values);
				amf_writer.write(values);
				k = 0x12;
			case MCuePoint(c):
				amf_writer.write( format.amf.Tools.encode("onCuePoint") );
				var values = format.amf.Tools.encode(c);
				amf_writer.write(values);
				k = 0x12;
			case MXMP(x):
				amf_writer.write( format.amf.Tools.encode("onXMPData") );
				var values = format.amf.Tools.encode(x);
				amf_writer.write(values);
				k = 0x12;
			case MCustom(n, d, _k):
				amf_writer.write( format.amf.Tools.encode(n) );
				var values = format.amf.Tools.encode(d);
				amf_writer.write(values);
				k = _k;
		}
		var d : Bytes = data.getBytes();
		
		return FLVMeta(d, time);//, timeExtended, streamid)); 
	}

}